Skip to content

Method: cloneUsingDefaultConstructor(Object, Field, Collection, CloneConfiguration)

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.sessions;
19:
20: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
21: import cz.cvut.kbss.jopa.model.annotations.Types;
22: import cz.cvut.kbss.jopa.model.metamodel.CollectionType;
23: import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection;
24: import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration;
25: import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor;
26: import cz.cvut.kbss.jopa.utils.CollectionFactory;
27: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
28: import cz.cvut.kbss.jopa.utils.MetamodelUtils;
29: import org.slf4j.Logger;
30: import org.slf4j.LoggerFactory;
31:
32: import java.lang.reflect.Constructor;
33: import java.lang.reflect.Field;
34: import java.lang.reflect.InvocationTargetException;
35: import java.util.ArrayList;
36: import java.util.Arrays;
37: import java.util.Collection;
38: import java.util.Collections;
39: import java.util.List;
40: import java.util.Optional;
41: import java.util.Set;
42:
43: /**
44: * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and
45: * thus they must be cloned specially. NOTE: This class may be removed in case a better cloning mechanisms (namely
46: * database mappings and copy policies) is introduced.
47: */
48: class CollectionInstanceBuilder extends AbstractInstanceBuilder {
49:
50: private static final Logger LOG = LoggerFactory.getLogger(CollectionInstanceBuilder.class);
51:
52: private static final Class<?> singletonListClass = Collections.singletonList(null).getClass();
53: private static final Class<?> singletonSetClass = Collections.singleton(null).getClass();
54: private static final Class<?> arrayAsListClass = Arrays.asList(null, null).getClass();
55:
56: CollectionInstanceBuilder(CloneBuilder builder, UnitOfWork uow) {
57: super(builder, uow);
58: }
59:
60: /**
61: * This method is the entry point for cloning the Java collections. It clones standard collections as well as
62: * immutable collections and singleton collections.
63: * <p>
64: * Currently supported are List and Set.
65: *
66: * @param collection The collection to clone
67: * @return A deep clone of the specified collection
68: */
69: @Override
70: Object buildClone(Object cloneOwner, Field field, Object collection, CloneConfiguration configuration) {
71: assert collection instanceof Collection;
72: Collection<?> container = (Collection<?>) collection;
73: if (container instanceof ChangeTrackingIndirectCollection<?>) {
74: container = (Collection<?>) ((ChangeTrackingIndirectCollection<?>) container).unwrap();
75: }
76: if (Collections.emptyList() == container || Collections.emptySet() == container) {
77: return container;
78: }
79: Object clone = cloneUsingDefaultConstructor(cloneOwner, field, container, configuration);
80: if (clone == null) {
81: clone = buildInstanceOfSpecialCollection(cloneOwner, field, container, configuration);
82: }
83: if (clone == null) {
84: clone = buildDefaultCollectionInstance(cloneOwner, field, container, configuration);
85: }
86: clone = uow.createIndirectCollection(clone, cloneOwner, field);
87: return clone;
88: }
89:
90: /**
91: * Clones the specified collection using its default zero argument constructor. If the specified collection has none
92: * (e. g. like SingletonList), this method returns null.
93: *
94: * @param container The collection to clone.
95: * @return cloned collection
96: */
97: private Collection<?> cloneUsingDefaultConstructor(Object cloneOwner, Field field,
98: Collection<?> container, CloneConfiguration configuration) {
99: Class<?> javaClass = container.getClass();
100: final Optional<Collection<?>> result = createNewInstance(javaClass, container.size());
101: // Makes shallow copy
102: result.ifPresent(r -> cloneCollectionContent(cloneOwner, field, container, r, configuration));
103: return result.orElse(null);
104: }
105:
106: private static Optional<Collection<?>> createNewInstance(Class<?> type, int size) {
107: Object[] params = null;
108: Class<?>[] types = {int.class};
109: // Look for constructor taking initial size as parameter
110: Constructor<?> ctor = getDeclaredConstructorFor(type, types);
111: if (ctor != null) {
112: params = new Object[1];
113: params[0] = size;
114: } else {
115: ctor = DefaultInstanceBuilder.getDeclaredConstructorFor(type, null);
116: }
117: if (ctor == null) {
118: return Optional.empty();
119: }
120: Collection<?> result = null;
121: try {
122: result = (Collection<?>) ctor.newInstance(params);
123: } catch (InstantiationException | InvocationTargetException | IllegalArgumentException e) {
124: throw new OWLPersistenceException(e);
125: } catch (IllegalAccessException e) {
126: logConstructorAccessException(ctor, e);
127: // Do nothing
128: }
129: return Optional.ofNullable(result);
130: }
131:
132: /**
133: * Clone all the elements in the collection. This will make sure that the cloning process creates a deep copy.
134: *
135: * @param source The collection to clone.
136: */
137: private void cloneCollectionContent(Object cloneOwner, Field field, Collection<?> source,
138: Collection<?> target, CloneConfiguration configuration) {
139: if (source.isEmpty()) {
140: return;
141: }
142: Collection<Object> tg = (Collection<Object>) target;
143: for (Object elem : source) {
144: if (elem == null) {
145: tg.add(null);
146: continue;
147: }
148: if (CloneBuilder.isImmutable(elem)) {
149: tg.addAll(source);
150: break;
151: }
152: tg.add(cloneCollectionElement(cloneOwner, field, elem, configuration));
153: }
154: }
155:
156: private Object cloneCollectionElement(Object cloneOwner, Field field, Object element,
157: CloneConfiguration configuration) {
158: Object clone;
159: if (builder.isTypeManaged(element.getClass())) {
160: clone = uow.registerExistingObject(element, new CloneRegistrationDescriptor(configuration.getDescriptor()).postCloneHandlers(configuration.getPostRegister()));
161: } else {
162: clone = builder.buildClone(cloneOwner, field, element, configuration.getDescriptor());
163: }
164: return clone;
165: }
166:
167:
168: private Collection<?> buildInstanceOfSpecialCollection(Object cloneOwner, Field field, Collection<?> container,
169: CloneConfiguration configuration) {
170: if (arrayAsListClass.isInstance(container)) {
171: final List<?> arrayList = new ArrayList<>(container.size());
172: cloneCollectionContent(cloneOwner, field, container, arrayList, configuration);
173: return arrayList;
174: } else if (singletonListClass.isInstance(container) || singletonSetClass.isInstance(container)) {
175: final Object element = container.iterator().next();
176: final Object elementClone = CloneBuilder.isImmutable(element) ? element :
177: cloneCollectionElement(cloneOwner, field, element, configuration);
178: final Collection<Object> result = CollectionFactory.createDefaultCollection(singletonListClass.isInstance(container) ? CollectionType.LIST : CollectionType.SET);
179: result.add(elementClone);
180: return result;
181: } else {
182: return null;
183: }
184: }
185:
186: private Collection<?> buildDefaultCollectionInstance(Object cloneOwner, Field field, Collection<?> container,
187: CloneConfiguration configuration) {
188: LOG.trace("Unable to find matching collection constructor. Creating default collection.");
189: final Collection<?> clone;
190: if (container instanceof List) {
191: clone = CollectionFactory.createDefaultCollection(CollectionType.LIST);
192: } else if (container instanceof Set) {
193: clone = CollectionFactory.createDefaultCollection(CollectionType.SET);
194: } else {
195: throw new OWLPersistenceException(
196: "Cannot clone unsupported collection instance of type " + container.getClass() + ".");
197: }
198: cloneCollectionContent(cloneOwner, field, container, clone, configuration);
199: return clone;
200: }
201:
202: @Override
203: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
204: assert originalValue == null || originalValue instanceof Collection;
205: assert cloneValue instanceof Collection;
206:
207: Collection<Object> clone = (Collection<Object>) cloneValue;
208: if (clone instanceof ChangeTrackingIndirectCollection) {
209: clone = ((ChangeTrackingIndirectCollection<Collection<Object>>) clone).unwrap();
210: }
211: Collection<Object> orig;
212: if (clone == Collections.emptyList() || clone == Collections.emptySet()) {
213: orig = createDefaultCollection(clone.getClass());
214: } else {
215: final Optional<Collection<?>> origOpt = createNewInstance(clone.getClass(), clone.size());
216: orig = (Collection<Object>) origOpt.orElse(createDefaultCollection(clone.getClass()));
217: }
218: EntityPropertiesUtils.setFieldValue(field, target, orig);
219:
220: if (clone.isEmpty()) {
221: return;
222: }
223: for (Object cl : clone) {
224: orig.add(uow.contains(cl) ? builder.getOriginal(cl) : cl);
225: }
226: final Types types = field.getAnnotation(Types.class);
227: if (types != null) {
228: MetamodelUtils.checkForModuleSignatureExtension(orig, builder.getMetamodel());
229: }
230: }
231:
232: private static Collection<Object> createDefaultCollection(Class<?> cls) {
233: return CollectionFactory.createDefaultCollection(CollectionType.fromClass(cls));
234: }
235:
236: @Override
237: boolean populatesAttributes() {
238: return true;
239: }
240: }